﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using UnityEngine;

class ServerApp : TSingleton<ServerApp>
{
    UdpClient m_Server;
    //Queue<KeyValuePair<IPEndPoint, byte[]>> m_RecvMsgs = new Queue<KeyValuePair<IPEndPoint, byte[]>>();
    Dictionary<IPEndPoint, Queue<byte[]>> m_RecvDict = new Dictionary<IPEndPoint, Queue<byte[]>>();
    public readonly List<UserSession> m_Users = new List<UserSession>();
    public readonly Dictionary<uint, UserSession> m_IDUsers = new Dictionary<uint, UserSession>();
    public readonly Dictionary<IPEndPoint, UserSession> m_IPUsers = new Dictionary<IPEndPoint, UserSession>();
    Queue<KeyValuePair<uint, byte[]>> m_WaitSendMsgs = new Queue<KeyValuePair<uint, byte[]>>();
    UniqueIDGenerator m_UserIDGen = new UniqueIDGenerator();
    public UniqueIDGenerator m_PlayerIDGen { private set; get; } = new UniqueIDGenerator();
    public System.Random m_Rand { private set; get; } = new System.Random();
    public uint m_TickCount;
    public float GameTime;
    public float m_DeltaTime;
    public List<Player> m_Players { private set; get; } = new List<Player>();
    public List<Player> m_Players_AI { private set; get; } = new List<Player>();
    Dictionary<uint, Player> m_PlayerDict = new Dictionary<uint, Player>();

    public void AddPlayer(Player p)
    {
        if (p.IsAI())
        {
            lock (m_Players_AI)
            {
                m_Players_AI.Add(p);
            }
        }
        else
        {
            lock (m_Players)
            {
                m_Players.Add(p);
            }
        }
        m_PlayerDict.Add(p.m_PlayerID, p);
    }

    public void Start()
    {
        MessageManager.Instance.Init();
        m_Server = new UdpClient(1234);

        Task.Run(() =>
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
            while (true)
            {
                try
                {
                    if (m_Server.Available > 0)
                    {
                        var bs = m_Server.Receive(ref ep);
                        lock (m_RecvDict)
                        {
                            if (!m_RecvDict.ContainsKey(ep))
                            {
                                m_RecvDict[new IPEndPoint(ep.Address, ep.Port)] = new Queue<byte[]>();
                            }
                            m_RecvDict[ep].Enqueue(bs);
                        }
                    }
                }
                catch (Exception ex)
                {
                    RemoveUser(ep);
                    LogManager.Error("ex.Message=" + ex.Message);
                }
            }
        });

        // recv process thread
        Task.Run(() =>
        {
            //KeyValuePair<IPEndPoint,>
            Stopwatch watch = new Stopwatch();
            while (true)
            {
                //while (m_RecvMsgs.Count > 0)
                //{

                //    m_RecvDict.Add(); 
                //}
                if (m_RecvDict.Count > 0)
                {
                    try
                    {
                        watch.Reset();
                        watch.Start();
                        LogManager.Log("[START] serverapp recv process");
                        lock (m_RecvDict)
                        {
                            foreach (var item in m_RecvDict)
                            {
                                if (item.Value.Count == 0)
                                {
                                    continue;
                                }

                                LogManager.Log("recv process thread=start item.Value.Count=" + item.Value.Count);
                                CheckAddUser(item.Key);
                                var u = GetUser(item.Key);
                                u.RecvMessage(item.Value);
                                item.Value.Clear();
                            }
                        }
                        LogManager.Log("[END] serverapp recv process TimeLapsed=" + watch.ElapsedMilliseconds);
                        watch.Stop();
                    }
                    catch (Exception ex)
                    {
                        LogManager.Error("ex.Message=" + ex.Message);
                    }
                }
                //Thread.Sleep(1);
            }
        });

        // send thread
        Task.Run(() =>
        {
            Stopwatch watch = new Stopwatch();
            while (true)
            {
                try
                {
                    if (m_Users.Count == 0)
                    {
                        continue;
                    }

                    lock (m_WaitSendMsgs)
                    {
                        watch.Reset();
                        watch.Start();
                        LogManager.Log("[START] serverapp send");
                        while (m_WaitSendMsgs.Count > 0)
                        {
                            LogManager.Log("send thread=start m_WaitSendMsgs.Count=" + m_WaitSendMsgs.Count);
                            var m = m_WaitSendMsgs.Dequeue();
                            if (m.Key == 0)
                            {
                                for (int i = m_Users.Count - 1; i >= 0; i--)
                                {
                                    var u = m_Users[i];
                                    try
                                    {
                                        m_Server.Send(m.Value, m.Value.Length, u.RemoteIP);
                                    }
                                    catch (Exception ex)
                                    {
                                        RemoveUser(u.RemoteIP);
                                        LogManager.Log("ex.Message=" + ex.Message);
                                    }
                                }
                            }
                            else
                            {
                                m_Server.Send(m.Value, m.Value.Length, GetUserIP(m.Key));
                            }
                        }
                        LogManager.Log("[END] serverapp send TimeLapsed=" + watch.ElapsedMilliseconds);
                        watch.Stop();
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log("ex.Message=" + ex.Message);
                }
                Thread.Sleep(1);
            }
        });

        // usersession recv process thread
        //Task.Run(() =>
        //{
        //    Stopwatch watch1 = new Stopwatch();
        //    UserSession u;
        //    while (true)
        //    {
        //        if (m_Users.Count == 0)
        //        {
        //            continue;
        //        }
        //        watch1.Reset();
        //        watch1.Start();
        //        LogManager.Log("[START] usersession recv process tickCount=" + m_TickCount);
        //        for (int i = m_Users.Count - 1; i >= 0; i--)
        //        {
        //            //if (m_TickCount % 6 != i % 6)
        //            //{
        //            //    continue;
        //            //}
        //            u = m_Users[i];
        //            u.RecvProcess();
        //        }
        //        LogManager.Log("[END] usersession recv process tickCount=" + m_TickCount + ", TimeLapsed=" + watch1.ElapsedMilliseconds);
        //        watch1.Stop();
        //        Thread.Sleep(1);
        //    }
        //});

        // tick thread
        Task.Run(() =>
        {
            Stopwatch watch1 = new Stopwatch();
            while (true)
            {
                try
                {
                    if (m_Users.Count == 0)
                    {
                        //continue;
                    }
                    // create ai
                    if (m_Players_AI.Count == 0)
                    {
                        int aiCount = 50;
                        for (int i = 0; i < aiCount; i++)
                        {
                            var p = new Player();
                            p.Init(null, m_PlayerIDGen.GetUniqueID(), GetRandomPos(5), GetRandomRot());
                        }
                        SendAIJoin();
                        LogManager.Log("S2C_AIJoinBatch count=" + aiCount);
                    }
                    watch1.Reset();
                    watch1.Start();
                    LogManager.Log("[START] tick tickCount=" + m_TickCount);

                    SendPlayerPosBatch();
                    SendAIPosBatch();
                    m_TickCount++;
                    LogManager.Log("[END] tick tickCount=" + m_TickCount + ", TimeLapsed=" + watch1.ElapsedMilliseconds);
                    watch1.Stop();

                    m_DeltaTime = MessageManager.Instance.GetCurElapsedTime() - GameTime;
                    GameTime = MessageManager.Instance.GetCurElapsedTime();

                    lock (m_Players_AI)
                    {
                        for (int i = m_Players_AI.Count - 1; i >= 0; i--)
                        {
                            var p = m_Players_AI[i];
                            if (p.IsAI())
                            {
                                p.TickAI();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Error("ex=" + ex.Message);
                }
                Thread.Sleep(33);
            }
        });

        // main thread
        Stopwatch watch = new Stopwatch();
        while (true)
        {
            if (m_Users.Count == 0)
            {
                continue;
            }
            watch.Reset();
            watch.Start();
            LogManager.Log("[START] mainthread, tickCount=" + m_TickCount);
            for (int i = m_Users.Count - 1; i >= 0; i--)
            {
                m_Users[i].Update();
            }
            LogManager.Log("[END] mainthread, tickCount=" + m_TickCount + ", TimeLapsed=" + watch.ElapsedMilliseconds);
            watch.Stop();
            Thread.Sleep(1);
        }
    }
    
    public void SendPlayerJoinBatch(uint userId)
    {
        for (int i = m_Users.Count - 1; i >= 0; i--)
        {
            var u = m_Users[i];
            if (u.UserID == userId)
            {
                continue;
            }

            S2C_PlayerJoin m = new S2C_PlayerJoin();
            m.UserId = u.UserID;
            m.PlayerId = u.m_Player.m_PlayerID;
            m.NickName = "玩家" + u.UserID;
            m.PosX = u.Pos.x;
            m.PosY = u.Pos.y;
            m.PosZ = u.Pos.z;
            m.RotY = u.RotY;
            m.TickCount = m_TickCount;
            Send(m,userId);
        }
    }

    void SendPlayerPosBatch()
    {
        var m = new S2C_PlayerSync();
        for (int i = ServerApp.Instance.m_Players.Count - 1; i >= 0; i--)
        {
            var player = ServerApp.Instance.m_Players[i];
            var p = new S2C_PlayerSyncSingle();
            p.PlayerId = player.m_PlayerID;
            p.PosX = player.m_Pos.x;
            p.PosY = player.m_Pos.y;
            p.PosZ = player.m_Pos.z;
            m.Players.Add(p);
        }

        var bs = m.ToByteArray();
        byte[] bs1 = new byte[bs.Length + 2];
        //Array.Copy(BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(m)), 0, bs1, 0, 2);
        //Array.Copy(bs, 0, bs1, 2, bs.Length);

        for (int i = m_Users.Count - 1; i >= 0; i--)
        {
            if (m_TickCount % 6 != i % 6)
            {
                continue;
            }
            var u = m_Users[i];
            m.TickCount = m_TickCount;
            Send(m, u.UserID);
            LogManager.Log("[ENTER TICK] send tick tickCount=" + m_TickCount + ", UserID=" + m_Users[i].UserID);
            //u.Tick();
        }
    }

    void SendAIPosBatch()
    {
        var m = new S2C_PlayerSyncAI();
        m.TickCount = m_TickCount;
        for (int i = ServerApp.Instance.m_Players_AI.Count - 1; i >= 0; i--)
        {
            var player = ServerApp.Instance.m_Players_AI[i];
            var p = new S2C_PlayerSyncSingleAI();
            p.PlayerId = player.m_PlayerID;
            p.PosX = player.m_Pos.x;
            p.PosY = player.m_Pos.y;
            p.PosZ = player.m_Pos.z;
            p.RotY = player.m_RotY;
            m.Players.Add(p);
        }

        //var bs = m.ToByteArray();
        //byte[] bs1 = new byte[bs.Length + 2];
        //Array.Copy(BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(m)), 0, bs1, 0, 2);
        //Array.Copy(bs, 0, bs1, 2, bs.Length);



        for (int i = m_Users.Count - 1; i >= 0; i--)
        {
            if (m_TickCount % 6 != i % 6)
            {
                continue;
            }
            var u = m_Users[i];
            m.TickCount = m_TickCount;
            Send(m, u.UserID);
            LogManager.Log("[ENTER TICK] send tick tickCount=" + m_TickCount + ", UserID=" + m_Users[i].UserID);
            //u.Tick();
        }
    }

    public void Send(byte[] bs, uint userId)
    {
        lock (m_WaitSendMsgs)
        {
            m_WaitSendMsgs.Enqueue(new KeyValuePair<uint, byte[]>(userId, bs));
        }
    }

    void Send(IMessage msg, uint userId = 0, bool isReliable = false)
    {
        if (userId == 0 && m_Users.Count > 0)
        {
            var u = m_Users[0];
            u.Send(msg, 0, isReliable);
            return;
        }

        for (int i = m_Users.Count - 1; i >= 0; i--)
        {
            var u = m_Users[i];
            u.Send(msg, userId, isReliable);
        }

        //var bs = msg.ToByteArray();
        //byte[] bs1 = new byte[bs.Length + 2];
        //Array.Copy(BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(msg)), 0, bs1, 0, 2);
        //Array.Copy(bs, 0, bs1, 2, bs.Length);
        //ServerApp.Instance.Send(bs1, userId);
    }

    public IPEndPoint GetUserIP(uint userId)
    {
        if (m_IDUsers.ContainsKey(userId))
        {
            UserSession u = m_IDUsers[userId];
            if (u.UserID == userId)
            {
                return u.RemoteIP;
            }
        }
        return null;
    }
    public UserSession GetUser(IPEndPoint ep)
    {
        if (m_IPUsers.ContainsKey(ep))
        {
            UserSession u = m_IPUsers[ep];
            if (MessageManager.Instance.IsRemoteIPIdentity(u.RemoteIP, ep))
            {
                return u;
            }
        }
        return null;
    }

    public void SendAIJoin(uint userId = 0)
    {
        // send ai data
        lock (m_Players_AI)
        {
            var ms = new S2C_AIJoinBatch();
            ms.TickCount = m_TickCount;
            for (int i = 0; i < m_Players_AI.Count; i++)
            {
                var p = m_Players_AI[i];
                // response. 
                S2C_AIJoin m = new S2C_AIJoin();
                m.PlayerId = p.m_PlayerID;
                m.NickName = "AI_" + p.m_PlayerID;
                m.PosX = p.m_Pos.x;
                m.PosY = p.m_Pos.y;
                m.PosZ = p.m_Pos.z;
                m.RotY = p.m_RotY;
                ms.Players.Add(m);
            }
            Send(ms, userId);
        }
    }

    void CheckAddUser(IPEndPoint ep)
    {
        if (!m_IPUsers.ContainsKey(ep))
        {
            var u = new UserSession(m_UserIDGen.GetUniqueID(), ep);
            LogManager.Log("CheckAddUser id=" + u.UserID);
            m_Users.Add(u);
            m_IPUsers.Add(ep, u);
            m_IDUsers.Add(u.UserID, u);
        }
    }

    public void RemoveUser(IPEndPoint ep)
    {
        if (m_IPUsers.ContainsKey(ep))
        {
            var u = m_IPUsers[ep];
            LogManager.Log("RemoveUser id=" + u.UserID);
            u.Stop(); 
            m_Users.Remove(u);
            m_IPUsers.Remove(ep);
            m_IDUsers.Remove(u.UserID);
            lock (m_RecvDict)
            {
                m_RecvDict.Remove(ep);
            }
        }
    }

    public Vector3 GetRandomPos(float radius)
    {
        return new Vector3(((float)ServerApp.Instance.m_Rand.NextDouble() - 0.5f) * 2, 0, ((float)ServerApp.Instance.m_Rand.NextDouble() - 0.5f) * 2) * radius;
    }

    public float GetRandomRot()
    {
        return ((float)ServerApp.Instance.m_Rand.NextDouble() - 0.5f) * 360;
    }
}
